////////////////////////////////////////////////////////////////////////////
//
//  CryEngine Source File.
//  Copyright (C), Crytek, 1999-2011.
// -------------------------------------------------------------------------
//  File name:	ConsoleHotUpdate.h
//  Version:	v1.00
//  Created:	03/03/2009 by Ivo Frey
//  Description:	Gaming console file copy and update thread
// -------------------------------------------------------------------------  
//  History:
//		17/01/2011 - Nicusor Nedelcu - refactored to use jobs & cmd system
//
//////////////////////////////////////////////////////////////////////////// 

#ifndef __ConsoleHotUpdate_h__
#define __ConsoleHotUpdate_h__
#pragma once

#include "CryThread.h"
#include "ResourceCompilerHelper.h"

class CConsoleHotUpdate : public CryThread<CConsoleHotUpdate>, public IResourceCompilerListener
{
	public:

		//! Used to specify settings for the file types
		struct SHotSyncFileType
		{
			SHotSyncFileType() :
				m_pCompileParams( "" ),
				m_bIgnore( false ),
				m_compileStageCount( 0 ),
				m_pOutputFileExtensions( NULL )
			{}

			SHotSyncFileType( const char* pCompileParams, bool bIgnore, uint32 aCompileStageCount = 0, const char* pRcOutputExtensions = NULL ) :
				m_pCompileParams( pCompileParams ),
				m_bIgnore( bIgnore ),
				m_compileStageCount( aCompileStageCount ),
				m_pOutputFileExtensions( pRcOutputExtensions )
			{}

			//! compile params given to RC
			const char*	m_pCompileParams;
			//! ignore changed files of this type
			bool				m_bIgnore;
			//! number of RC compile steps for this file type, 0 is no compilation needed
			uint32			m_compileStageCount;
			//! possible file extensions to look for after RC run
			const char*	m_pOutputFileExtensions;
		};	

		//! A console hot update job public handle
		typedef void* TJobHandle;

		//! Used to catch events from a job
		struct SHotUpdateJobObserver
		{
			enum EResultCode
			{
				eResultCode_UploadSuccessful,
				eResultCode_NeedsCompiling,
				eResultCode_UploadError,
				eResultCode_ExtensionNotSupported,
				eResultCode_OperationAborted,
				eResultCode_SkippedPlatform
			};

			//! called when upload starts
			virtual void OnUploadStart(){};
			//! called when upload ends
			virtual void OnUploadEnd( bool bAborted = false ){};
			//! called each file to show progress
			virtual void OnUploadFileProgress( CHotUpdateSystem::EConsolePlatform aPlatform, const char* pCurrentFilename, UINT aUploadedFileCount, UINT aTotalFileCount, EResultCode aResult, const char* pAdditionalResultMsg = "" ){};
			//! called when an RC message is sent
			virtual void OnRcMessage( CHotUpdateSystem::EConsolePlatform aPlatform, IResourceCompilerListener::MessageSeverity aSeverity, const char* pMsg ){};
		};

		//! A hot update command, enqueued in a job's command list to be executed
		struct SHotUpdateCommand
		{
			enum EType
			{
				eType_None,
				eType_FileCopy,
				eType_DeleteTempFile,
				eType_ResetSoft,
				eType_ResetHard,
				eType_LaunchGame
			};

			SHotUpdateCommand( EType aType = eType_None, long aCreationTime = 0 )
			{
				m_type = aType;
				m_commandCreationTime = aCreationTime;
			}

			//! \return true if the command is ready to be processed
			virtual const bool IsReadyForProcessing() const
			{
				return true;
			}

			SHotUpdateCommand::EType	m_type;
			//! the tick count (msec) time when this command was created
			long											m_commandCreationTime;
		};

		//! A file copy to console command
		struct SHotUpdateCommandFileCopy : SHotUpdateCommand
		{
			SHotUpdateCommandFileCopy( ULONGLONG uid, const char* pOriginalFilename, long aCreationTime = GetTickCount() )
			{
				m_type = eType_FileCopy;
				m_commandCreationTime = aCreationTime;
				m_originalFileName = pOriginalFilename;
				m_uid = uid;
			}

			const bool IsReadyForProcessing() const
			{
				const int kWaitTimeAfterAdd = 5000; //msec
				//TODO: check if file is not opened in write mode by other processes/threads
				return ( GetTickCount() - m_commandCreationTime ) > kWaitTimeAfterAdd;
			}

			CString			m_originalFileName, m_tempFileName;
			ULONGLONG		m_uid;
		};

		//! A delete temp file command, added to command list after the file is copied to consoles
		struct SHotUpdateCommandDeleteTempFile : SHotUpdateCommand
		{
			SHotUpdateCommandDeleteTempFile( const char* pTempFilename, long aCreationTime = GetTickCount() )
			{
				m_type = eType_DeleteTempFile;
				m_commandCreationTime = aCreationTime;
				m_tempFileName = pTempFilename;
				Log( "ConsoleHotUpdate: Added temp file for deletion: '%s'", pTempFilename );
			}

			const bool IsReadyForProcessing() const
			{
				const int kWaitTimeAfterAdd = 5000; //msec
				//TODO: check if file is not opened in write mode by other processes/threads
				return ( GetTickCount() - m_commandCreationTime ) > kWaitTimeAfterAdd;
			}

			CString	m_tempFileName;
		};

		//! A hot update job, executing commands from a queue, one by one
		struct SHotUpdateJob
		{
			SHotUpdateJob():
				m_pObserver( NULL )
			{}

			~SHotUpdateJob()
			{
				DeleteCommands();
			}

			void DeleteCommands()
			{
				for( size_t i = 0, iCount = m_commands.size(); i < iCount; ++i )
				{
					delete m_commands[i];
				}

				m_commands.clear();
			}

			size_t ComputeFileCopyCmdCount();

			//! list of commands
			std::vector<SHotUpdateCommand*>	m_commands;
			//! the observer of this job
			SHotUpdateJobObserver*					m_pObserver;
			//! the number of files from the commands list, used in progress info display
			UINT														m_totalFileCount;
		};

		typedef std::map<SHotUpdateJob*,std::vector<SHotUpdateCommand*>> TNewJobCommands;

		static CConsoleHotUpdate& Instance()
		{
			static CConsoleHotUpdate instance;
			return instance;
		}

		//! add a filename to a specified file transfer job or to the main file tranfer job
		//! @param pFilename filename to add to job
		//! @param hJob if its 0 then will create a new job, otherwise will add the file to that job
		//! @param pObserver the observer for the job
		//! @note if hJob and pObserver are NULL, then the file will be added to the main job
		TJobHandle	AddFile( const char* pFilename, TJobHandle hJob = 0, SHotUpdateJobObserver* pObserver = NULL );
		//! add filenames to a specified file transfer job or to the main file tranfer job
		//! @param rFilenames filenames to add to the job
		//! @param hJob if its 0 then will create a new job, otherwise will add the files to that job
		//! @param pObserver the observer for the job
		//! @note if hJob and pObserver are NULL, then the file will be added to the main job
		TJobHandle	AddFiles( const std::set<CString>& rFilenames, TJobHandle hJob = 0, SHotUpdateJobObserver* pObserver = NULL );
		//! add a reset consoles command to a job
		//! @param bHardReset true to reset the console in HARD mode (see console devkits docs),
		//!										'false' is the recommended value to perform a SOFT reset (allow OS to close file handles safely)
		//! @param hJob if its 0 then will create a new job, otherwise will add the files to that job
		//! @param pObserver the observer for the job
		//! @note if hJob and pObserver are NULL, then the file will be added to the main job
		TJobHandle	AddResetCommand( bool bHardReset = false, TJobHandle hJob = 0, SHotUpdateJobObserver* pObserver = NULL );
		//! add a launch game level console command to a job
		//! @param hJob if its 0 then will create a new job, otherwise will add the files to that job
		//! @param pObserver the observer for the job
		//! @note if hJob and pObserver are NULL, then the file will be added to the main job
		TJobHandle	AddLaunchCommand( TJobHandle hJob = 0, SHotUpdateJobObserver* pObserver = NULL );
		//! export current level files and launch the game into that level
		void				ExportAndLaunch();
		//! if pJob is 0, will abort all file transfer jobs
		void				AbortJob( TJobHandle hJob );
		//! delete a file transfer job
		bool				DeleteJob( TJobHandle hJob );
		//! @return true if a job is done uploading
		bool				IsJobDone( TJobHandle hJob );
		//! pause the command processing
		void				CommandProcessingPause();
		//! resume the command processing
		void				CommandProcessingResume();
		//! reset the consoles directly, without adding the command in the queue
		//! @param bHard hard reset
		bool				ResetConsoles( bool bHard = false );

		// from CryThread
		void				Run();
		void				Begin();
		void				End();
		void				Resume();

	private:

		CConsoleHotUpdate();
		~CConsoleHotUpdate();

		//! sync the main thread data and the command processing thread data (copy new commands over to thread's lists)
		void SyncThreadData();
		//! if the given path is a file in a PAK, extract it
		//! @param uid the unique id of the temp file
		//! @param pFilename the filename to be checked if in pak, and to be extracted to game folder if so
		//! @param rOutNewFilename the new path for the file (extracted)
		//! @param bForceOverwriteDiskFile if true, then it will overwrite any existing disk files if already there
		bool PrepareFileForTransfer( ULONGLONG uid, const char* pFilename, CString& rOutNewFilename, bool& bExistsInPak /*[out]*/, CString& rAdjustedOriginalFilename /*[out]*/ );
		//! process a hot update command
		bool ProcessCommand( SHotUpdateJob* pJob, SHotUpdateCommand* const pCmd );
		//! process next command in the job
		void ProcessNextCommand( SHotUpdateJob* pJob );
		//! \return true if extension needs to be ignored
		bool IgnoreFileExtension( const char* pFileExtension );
		//! \return true if the file copy operation is already in the job's command queue
		bool IsFileAlreadyInQueue( SHotUpdateJob* pJob, const char* pFilename );
		//! \return true if the file extension must be compiled by the RC
		bool NeedsCompiling( const char* pFileExtension );
		//! \return true if this file extension needs multipass compiling by the RC
		bool NeedsMultipassCompiling( const char* pFileExtension );
		//! \return true if RC compile options were returned successfuly in rOptions
		bool GetCompileOptions( const char* pFileExtension, CString& rOptions );
		//! \return the file type info by extension
		SHotSyncFileType* GetHotSyncFileType( const char* pFileExtension );
		//! \return true if executing the RC for the given filename was ok
		bool ExecuteResourceCompiler( CHotUpdateSystem::EConsolePlatform aPlatform, const char* pFilename, std::vector<CString>& rOutputFilenames, const char* pFileExtension );
		//! \return true if asset was copied successfuly to console
		bool AssetCopy( CHotUpdateSystem::EConsolePlatform aPlatform, const char* pOriginalFilename, std::vector<CString>& rFilenames, ULONGLONG uid = 0 );
		//! \return true if asset change was been notified successfuly to the console
		bool AssetNotifyChange( CHotUpdateSystem::EConsolePlatform aPlatform, const char* pOriginalFilename );
		//! called by the RC to output message
		void OnRCMessage( MessageSeverity aSeverity, const char* pText );

		//! map of file extensions (lowercase) and settings for it
		static std::map<CString,SHotSyncFileType>	s_fileTypes;
		//! used to generate a unique number for temp files across the session
		static ULONGLONG													s_tempFileUidCounter;
		//! run and pause thread
		bool																			m_bRun, m_bPaused;
		//! the temp folder for console hot update files
		CString																		m_strConsoleHotUpdateTempFolder;
		//! current RC platform for OnRCMessage callback
		CHotUpdateSystem::EConsolePlatform				m_currentRcPlatform;
		//! the mutex used to sync data (new commands) between main thread and hot update thread
		CryMutex																	m_syncMutex;
		//! current job being processed
		SHotUpdateJob*														m_pCurrentJob;
		//! the hot update jobs
		std::vector<SHotUpdateJob*>								m_jobs; // hot update thread
		//! the the main job, where default commands go (those added without a job handle)
		SHotUpdateJob															m_mainJob; // hot update thread
		//! the new jobs to be added (handled by main thread)
		std::vector<SHotUpdateJob*>								m_newJobs; // main thread
		//! the new commands to be added to existing jobs (handled by main thread)
		TNewJobCommands														m_newJobCommands; // main thread
		//! the main job commands to be added (handled by main thread)
		SHotUpdateJob															m_mainJobInfo; // main thread
		//! the jobs which are done (handled by main thread)
		std::map<TJobHandle,bool>									m_jobsDone; // main thread
		//! the jobs to be deleted and to be aborted (handled by main thread)
		std::set<TJobHandle>											m_jobsToDelete, m_jobsToAbort; // main thread
		//! the job used for export and launch level
		TJobHandle																m_hExportAndLaunchJob;
};

#endif //__ConsoleHotUpdate_h__
